[contents] [next] [bottom] (1 out of 5)

Chapter 3 - Working with Objects

ScriptX is an object-oriented language that operates on a hierarchy of classes and objects. The language makes no distinction between the core set of ScriptX classes and the classes and objects that you define yourself. Creating new instances, accessing and changing instance variables, and calling methods are operations which are used with all classes. This chapter contains an overview of the basic operations on classes and objects in ScriptX.

Creating New Objects

In ScriptX, all information you work with in the process of writing a program is contained in objects. Objects can be created in many ways in ScriptX. Objects are created as literals, as the result of a function call to other objects, as a side effect of those functions, or explicitly by you in a script. This latter case is the focus of this section.

ScriptX provides three primary ways in which you can directly create new objects:

A Note On Memory Allocation

Memory allocation in ScriptX is automatic. When you create a new object, the appropriate amount of memory is allocated for that object in the system.

Literals

Literals are special constructs in the language that allow you to represent many common objects directly in a script. Numbers, strings, and array constructs, as described in the previous chapter, are all literals. Here are some examples:

new

Only a few select classes in the ScriptX core classes have literal representations in the ScriptX language. The most common way to create an instance of any class in a script is by using the new generic function:

new class [ key:value key:value . . . ]
Function calls are discussed later in this chapter, beginning on page 63. The calling sequence for the generic function new has three components:

Keyword arguments specify initial parameters of the new object, for example, its initial state or size. Often, these parameters set initial values for instance variables. The new method creates the instance and then passes those keyword arguments internally to the object's init generic function. The keyword arguments that a class allows are defined by that class's init method, and by all of its superclasses which implement an init method.

Each class definition in the ScriptX Class Reference specifies the keyword arguments that are used by that class. Scripted classes also define keyword arguments, and they can specialize any behavior they inherit. For example, a scripted class can supply different default values for a keyword argument it inherits. For more information on specializing the init method, see page 132.

Keyword arguments can be specified in any order, and many of them are optional. Any optional keywords you do not specify in calling new are given default values by the init method when the object is initialized.

The init method determines which keyword arguments are optional and what the default values are. Each init method applies the init method defined by its superclasses. Superclasses may define other keyword arguments. The init method may specialize or override any behavior it inherits from its superclasses. In this way, a class's init method, together with that of its superclasses, determines which keyword arguments are defined, which are required or optional, and what the default values are.

Many classes define no keyword arguments:

myStencil := new Path 
new LinkedList

If some keywords are optional, you can specify values for only the ones you are concerned with, and allow the others to be initialized to defaults:

myRect2 := new Rect x2:500 y2:500 -- x1 and y1 are set by default
bigArray := new Array initialSize:150 -- growable is set by default

Because new evaluates to a new instance of the given class, you can nest expressions using new within other expressions:

rectangular := new TwoDShape stencil:(new Rect x2:500 y2:500)

The new Rect expression evaluates to an instance of the Rect class, which is then used as the value for the stencil keyword argument to the TwoDShape class. By nesting new, you write more concise code and avoid extra variables.

Note that the new generic is not a part of the ScriptX syntax. It is actually a generic function, a generic you call on class objects. Recall that in the ScriptX object system, classes are themselves objects. You can call the new generic function on any concrete class to create an instance of that class. (Concrete and abstract classes are defined on page 23.)

In most cases, ScriptX reports an exception if you attempt to call new on an abstract class. However, in some special cases the Kaleida Media Player creates a new instance of a concrete subclass of that abstract class automatically. (Examples include KeyboardDevice and MouseDevice.)

For more information on new, see the "Overview" chapter of the ScriptX Class Reference. For information on the object system, see the "Object System Kernel" chapter of ScriptX Components Guide.

Using the object Expression

An alternative to the new generic for creating new instances of a class is to use the object definition construct. The object construct allows you to give initial values to any instance variables, including those that are not initialized by keyword arguments.

The complete form of the object definition expression allows you to fully specialize a new object. You can add new instance variables, define new methods, and even create an object that is an instance of a mixture of classes. This chapter describes only some aspects of this expression. For more information, see Chapter 6, "Defining Classes and Objects." The simplest form the object expression creates a new instance of a class. Just as with new, the object expression allows you to specify any available or required keyword arguments:

object [ variableName ] ( classes ) 
[
keyword:value, keyword:value, . . . ]
end
In the object expression, classes is the class or list of classes this object is an instance of. It can be a list of several classes, separated by commas. variableName is an optional variable to which you can assign this object. The keyword:value pairs are any keyword arguments. As with the generic function new, you can specify keyword arguments in any order. You can specify keyword arguments on the same line, separated by commas, on separate lines, or in any combination:

object (Rect) x1:35, y1:35, x2:50, y2:50 end

object (Rect)
x1:35, y1:35
x2:50, y2:50
end

object (Array)
initialSize:100
growSize:10
end

The object expression evaluates to an instance of the new class, which allows it to be nested within other expressions. You can also assign the new object to a variable, using one of two forms: you can specify a variableName in the first line of the object expression, or you can put the variable name on the left side of an assignment statement, with the object expression on the right.

object myNewRect (Rect) x2:100, y2:100 end -- constant variable
myOtherNewRect := object (Rect) x2:100, y2:100 end -- not constant

The difference between these two forms is that the first form, in which you include the variable name inside the object expression, causes the variable myNewRect to be declared constant. This prevents you from assigning anything else to that variable once the object has been created. You can, however, use the object expression multiple times to redefine the object assigned to a variable.

Initializing Instance Variables

You can use the object expression to initialize the values of any instance variables defined by that object. In many cases, the object may use keyword arguments to give many of its instance variables initial values. However, if there are other instance variables defined by the object that are not set directly by keyword arguments, you can specify initial values for those instance variables as well.

object [ variableName ](classes) 
[
keyword:value, keyword:value, . . . ]
settings
variable:value
. . .
end
The settings reserved word, which should always occur just after any keyword arguments in an object expression, indicates that the following instance variables should be initially set. You can then specify any number of variable:value pairs. Just as with the keyword arguments, these variable:value pairs can be specified on a single line separated by commas, on separate lines, or in any combination.

For example, the class TwoDShape defines instance variables for its fill and stroke values as well as variables for the x and y coordinates of the shape. The init method for TwoDShape defines keyword arguments for fill and stroke, but not for the coordinates. This example uses keyword arguments to set the fill and stroke for the shape, and then settings to initialize the values of x and y.

rect1 := object (TwoDShape)
	fill:(new Brush color:redColor) -- keyword argument
	stroke:(new Brush color:greenColor) -- keyword argument
	settings
		x:50, y:50
end

Instance Variable Access

Instance variables define the attributes or properties of an object. For example, instance variables may be used to hold information such as coordinate position (for graphic objects), tempo and number of channels (for audio objects), or form of compression (for video objects).

Many instance variables are assigned initial values through keyword arguments to the new generic or the object definition construct. Once the object has been created, you can use the language constructs described in these sections to get and set the values of those instance variables.

Variable Access Expression

You query the value of an instance variable by using the dot syntax:

object.instanceVariable
object is an object (or an expression that yields an object), and instanceVariable is the name of the instance variable.

In the following example, a new instance of the class Line is created. Instances of Line have four instance variables for the coordinates of their starting and ending points: x1, y1 and x2, y2. This example uses keyword arguments to set those variables, and then it accesses those values:

myLine := new Line x1:10 y1:10 x2:65 y2:65
myLine.x1 
10
-- test: is the endpoint higher than the startpoint?
myLine.y2 < myLine.y1
false

If an object's instance variable holds another object that in turn has its own instance variables, you can "chain" references to those variables:

object myRectangularShape (TwoDShape)
stroke:blackBrush, boundary:(new Rect x2:100 y2:100)
end
myRectangularShape.boundary.x2 
100

Note that in order to specify most complex expressions as the object part of the instance variable construct, you must specify that expression inside parentheses so that it is evaluated first. The only expressions that can be used without parentheses as the object part of an instance variable expression are:

Instance Variable Access-An Efficiency Note

Since all access to instance variables is through method calls (see "Setters, Getters, and Real and Virtual Variables" on page 141) it is best to avoid repeated calls to an object nested several levels deep.

Using an example from Chapter 1, we got the address of Odan's owner using the construct odan.owner.address. This involves two function calls, one for each ".". Let's say you want to send out a mailing to every dog owner living in the same city as Odan's owner, so you need to compare each address in your list to Odan's address. You could greatly improve efficiency by assigning Odan's address to a variable and then using that variable for comparison, thus accessing the address just once instead of every time you make a comparison. The following code fragment, though not complete, illustrates the point:

address := odan.owner.address -- assign to a variable
for i := 1 to addressList.size do -- create a for loop to iterate over 
										-- an array of addresses
	if addressList[i] = address do -- compare each item to variable
		... -- your function for sending mail

A further efficiency could be achieved by using the Collection method forEach in place of the for loop, but the for loop is easier to understand in an example at this point.

Changing the Values of Instance Variables

Use the assignment operator ( := ) to set the value of an instance variable:

object.instanceVariable := value
As before, object is an object or an expression that yields an object, instanceVariable is the name of the instance variable, and value is an expression that sets the new value.

myLine := new Line x1:10 y1:10 x2:65 y2:65
-- Change the line's starting point to be 0,0
myLine.x1 := 0
myLine.y1 := 0
-- Change the line's endpoint to be 10,10
myLine.x2 := 10
myLine.y2 := 10

Most instance variables are read-write. That is, you can both query their values and change them. A few are read-only. If you try to change them, an error is reported. Whether an instance variable is read-write or read-only is defined by the class.

Class Variables

Class variables are similar to instance variables, except that they are variables defined by an entire class, rather than by a single instance of the class. Think of class variables as instance variables for the class itself. To query or change the value of a class variable, use the same ScriptX construct as for instance variables, specifying the class (or an expression that yields a class) instead of an object:

class.varname
For example, interests is a class variable defined by the Event class. The result of entering the following code is a collection of the interests that have been posted in myEvent.

myEvent.interests

Calling Functions

There are two kinds of functions in ScriptX: regular functions and generic functions.

Regular functions are just like functions in other languages; they have a single interface (its name and arguments), and a single implementation (the function definition), as shown in Figure 3-1.

Figure 3-1: Regular functions

Generic functions are used to invoke methods that are defined by classes and objects. Unlike regular functions, generic functions have a single interface, but they can select from many different implementations of that function (methods) based on the object specified as the first argument. When you call a generic function, it selects an appropriate method to invoke and transfers control to that method, passing along any arguments you specified (Figure 3-2). For a definition of generic functions, see the discussion on page 17 and page 10.

Figure 3-2: Generic functions

In Figure 3-2, prin is a generic function that takes three arguments, an object to print, an argument that determines how to print it, and a stream object (prin is described in detail on page 73). Since each class has a different printable representation, there are separate methods in each class that implement prin. The generic function prin uses the class of the object (myArray, myRect, and myString, instances of Array, Rect, and String) to choose the appropriate implementation.

Both regular and generic functions are called identically in scripts. There are three forms of function calls:

functionName   positionalArgs   keywordArgs 
functionName(positionalArgs, keywordArgs)
functionName()
The first two forms are used to call regular or generic functions with arguments. Both forms are equivalent and are described below. The third form is used to call regular functions without any arguments This third form cannot be used with generic functions because they require at least one argument-the object the generic function is to operate on. The empty argument list () is used to indicate to ScriptX that this is a function call and not a variable reference.

In the first two forms, positionalArgs are any required positional arguments for that function, and keywordArgs are any keyword arguments that the function or generic function may have defined. Keyword arguments are key:value pairs, where the key and the value are separated by a colon. Positional arguments, if any, must appear before any keyword arguments and must be specified in the correct order. Keyword arguments, if any are defined, can appear in any order.

The second form for calling functions is one that may seem more familiar to those who are accustomed to other programming languages. In this form, all the arguments, positional or keyword, are separated by commas and surrounded by parentheses.

The following examples illustrate calling both regular and generic functions. These examples use the following functions:

cheez := #("swiss", "cheddar", "roquefort")
append cheez "havarti" -- returns the key of the item added to cheez
4
getNth cheez 3
"roquefort"
getNth(cheez, 4)
"havarti"
getClassName cheez
"Array"
helloWorld() -- function with no args
"Hi mom!"
hamCheeseOnRye meat:@liverwurst cheese:@swiss bread:@rye
--** "@liverwurst is not an appropriate sandwich ingredient"
hamCheeseOnRye(meat:@ham, cheese:@swiss, bread:@wholewheat)
--** "@wholewheat is not an appropriate sandwich ingredient"
hamCheeseOnRye meat:@ham cheese:@swiss bread:@rye
"a most excellent sandwich."


This document is part of the ScriptX Language Guide, one of the volumes of the ScriptX Technical Reference Series. ScriptX is developed by the ScriptX Engineering Team at Apple Computer, successor to the Kaleida Engineering Team at Kaleida Labs, Inc.

Copyright 1996 Apple Computer, Inc. All Rights Reserved.